package com.ho1ho.springboot.framework.core.interceptors;

import java.util.Enumeration;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class ExecuteTimeInterceptor implements HandlerInterceptor {

	private static final String FOUND_RESOUSE_REQUEST = "Found ResourceHttpRequestHandler.";

	private static final long CONTROLLER_EXECUTE_TIME_THRESHOLD = 1000;

	private static final long REQUEST_EXECUTE_TIME_THRESHOLD = 2000;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		// log.trace("ExecuteTimeInterceptor preHandle");
		long startTime = System.currentTimeMillis();
		long requestId = System.nanoTime();

		// Do not log resource http request just like:
		// /resources/webjars/
		// META-INF/resources/
		// resources/
		// static/
		// public/
		if (handler instanceof ResourceHttpRequestHandler) {
			return true;
		}

		// String resultClassString = getResultClassString(handler);
		String requestMethod = request.getMethod();
		String requestUri = request.getRequestURI();
		if (StringUtils.isNotBlank(request.getQueryString())) {
			requestUri += getParameters(request);
		}

		// String ip = RequestUtil.getIpAddress(request);

		// log.info("========== Start - {} - [{}-{}] Execution", requestUri, requestId, resultClassString);
		// log.info("========== Start [{} {}]-[{}] ==========", requestMethod, requestUri, requestId);
		log.info("=== Start [{} {}]", requestMethod, requestUri);
		request.setAttribute("startTime", startTime);
		request.setAttribute("requestUniqId", requestId);
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
		if (handler instanceof ResourceHttpRequestHandler) {
			return;
		}

		long startTime = 0;
		try {
			startTime = (Long) request.getAttribute("startTime");
		} catch (Exception e) {
			log.warn("postHandle() Can not get startTime from request. Message: {}", e.getMessage());
			return;
		}
		long endTime = System.currentTimeMillis();

		long executeTime = endTime - startTime;
		request.setAttribute("controllerExecuteTime", executeTime);
		// log.trace("ExecuteTimeInterceptor postHandle");
	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		if (handler instanceof ResourceHttpRequestHandler) {
			return;
		}

		// long requestId = (Long) request.getAttribute("requestUniqId");
		long startTime = 0;
		long controllerExecuteTime = 0;
		boolean hasException = false;
		try {
			startTime = (Long) request.getAttribute("startTime");
			controllerExecuteTime = (Long) request.getAttribute("controllerExecuteTime");
		} catch (Exception e) {
			hasException = true;
			log.warn("afterCompletion() Can not get startTime or controllerExecuteTime from request. Message: {}", e.getMessage());
		}

		// String resultClassString = getResultClassString(handler);
		String requestMethod = request.getMethod();
		String requestUri = request.getRequestURI();
		if (StringUtils.isNotBlank(request.getQueryString())) {
			requestUri += getParameters(request);
		}

		// String ip = RequestUtil.getIpAddress(request);

		long requestEndTime = System.currentTimeMillis();
		long requestExecuteTime = requestEndTime - startTime;
		// if (modelAndView != null) {
		// modelAndView.addObject("executeTime", executeTime);
		// }

		// You can not set value to request because this method will be called after view rendered.
		// request.setAttribute("requestExecuteTime", requestExecuteTime);

		// String paddingControllerExecuteTime = StringUtils.leftPad(String.valueOf(controllerExecuteTime), 4);
		// String paddingRequestExecuteTime = StringUtils.leftPad(String.valueOf(requestExecuteTime), 4);

		// log.info("============ End [{}-{}] Execution - {} ms", requestId, resultClassString, executeTime);
		if (hasException) {
			log.warn("===== End [{} {}]-Can not calculate execute time.", requestMethod, requestUri);
			return;
		}

		if (controllerExecuteTime > CONTROLLER_EXECUTE_TIME_THRESHOLD || requestExecuteTime > REQUEST_EXECUTE_TIME_THRESHOLD) {
			// log.warn("========== End - Slow Request - [{} {}]-[{}] {}ms ==========", requestMethod, requestUri, requestId,
			// executeTime);
			log.warn("===== End [{} {}]-Slow Request-[{}ms][{}ms]", requestMethod, requestUri, controllerExecuteTime, requestExecuteTime);
		} else {
			// log.info("========== End [{} {}]-[{}] {}ms ==========", requestMethod, requestUri, requestId, executeTime);
			log.info("===== End [{} {}]-[{}ms][{}ms]", requestMethod, requestUri, controllerExecuteTime, requestExecuteTime);
		}
		// log.trace("ExecuteTimeInterceptor afterCompletion");
	}

	@SuppressWarnings("unused")
	private String getResultClassString(Object handler) {
		String resultClassString = handler.toString();

		if (handler instanceof HandlerMethod) {
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			if (handlerMethod != null) {
				Object obj = handlerMethod.getBean();
				if (obj instanceof BasicErrorController) {
					resultClassString = "ERROR";
				} else {
					resultClassString = obj.getClass().getName();
				}
			}
		} else if (handler instanceof ResourceHttpRequestHandler) {
			ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler;
			resultClassString = FOUND_RESOUSE_REQUEST;
			List<Resource> locations = resourceHandler.getLocations();
			if (locations != null && !locations.isEmpty()) {
				int locationSize = locations.size();
				StringBuffer sb = new StringBuffer();
				sb.append(" Resource size: ");
				sb.append(locationSize);
				sb.append("  Resource list: ");
				for (Resource resource : locations) {
					sb.append(resource.getDescription());
					sb.append("|");
				}
				resultClassString += sb.toString();

			}
		}
		return resultClassString;
	}

	private String getParameters(HttpServletRequest request) {
		StringBuffer posted = new StringBuffer();
		Enumeration<?> e = request.getParameterNames();
		if (e != null) {
			posted.append("?");
		}
		while (e.hasMoreElements()) {
			if (posted.length() > 1) {
				posted.append("&");
			}
			String curr = (String) e.nextElement();
			posted.append(curr);
			if (curr.contains("password") || curr.contains("pass") || curr.contains("pwd")) {
				posted.append("=*****");
			} else {
				String param = request.getParameter(curr);
				if (StringUtils.isNotBlank(param)) {
					posted.append("=" + param);
				}
			}
		}

		return posted.toString();
	}
}